/*
 * Author: KevinKW
 * Abstract: Implementation of zigbcomm
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <string>

#include <vector>
#include <stack>
#include <utility>
#include <set>
#include <pthread.h>
#include <string.h>

#include "klog.hpp"

using namespace std;
using namespace KLOG;

namespace KLOG {
#define TIME_FMT "%Y%m%d%H%M%S"
    void kLog::getFileName(char* name) {
        sprintf(name, "%s/%s",
                logPath, fileName);
    }
    bool kLog::buildFileNameList() {
        struct fileItem {
            char* name;
            time_t t;
            struct fileItem* next;
        };
        DIR* d = opendir(logPath);
        if (!d) {
            fprintf(stderr, "Error: Open log path %s failed! (%s)\n",
                    logPath, strerror(errno));
            return false;
        }
        struct dirent* de;
        struct fileItem *fiHead = NULL;
        while ((de = readdir(d)) != NULL) {
            if ((strncmp(de->d_name, fileName, fileNameLen) == 0) &&
                    (strlen(de->d_name) == (fileNameLen + 15))) {
                // Match the filename
                char *p = de->d_name + fileNameLen + 1;
                struct tm t;
                if (strptime(p, TIME_FMT, &t) == NULL) {
                    //fprintf(stderr, "Error: Parse time failed! (%s)\n", strerror(errno));
                    //break;
                    // If the parse failed, then skip it.
                    continue;
                }

                char *file = new char[PATHLEN];
                if (!file) {
                    fprintf(stderr, "Error: new file name failed\n");
                    // break;
                    // If allocate memory failed, then skip it.
                    continue;
                }

                struct fileItem *fi = new struct fileItem;
                if (!fi) {
                    fprintf(stderr, "Error: new file item failed\n");
                    // break;
                    // If allocate memory failed, then skip it.
                    delete [] file;
                    continue;
                }
                snprintf(file, PATHLEN, de->d_name);
                fi->name = file;
                fi->t = mktime(&t);
                fi->next = NULL;

                // Insert to the fileItem list sorted
                struct fileItem *fip = fiHead;
                struct fileItem *fipp = fiHead;
                while (fip) {
                    if (fip->t < fi->t) {
                        break;
                    }
                    fipp = fip;
                    fip = fip->next;
                }

                if (!fiHead) { // Empty list;
                    fiHead = fi;
                } else if (fip == fipp) { // First one
                    fi->next = fiHead;
                    fiHead = fi;
                } else {
                    fipp->next = fi;
                    fi->next = fip;
                }
            }
        }
        struct fileItem *fi = fiHead;
        unsigned int fn = maxLogFileNum - 1;
        stack<char*> s;
        // Push the newest filename
        while(fi && (fn != 0)) {
            s.push(fi->name);
            fiHead = fiHead->next;
            delete fi;
            fi = fiHead;
            fn--;
            logFileNum++;
        }
        while (!s.empty()) {
            char *tmp = s.top();
            fileNames.push(tmp);
            s.pop();
        }
        // Remove the rest
        while (fi) {
            char file[PATHLEN];
            snprintf(file, PATHLEN, "%s/%s", logPath, fi->name);
            if (remove(file) < 0) {
                fprintf(stderr, "Remove oldest log file %s error! (%s)\n",
                        file, strerror(errno));
            }

            fiHead = fiHead->next;
            delete [] fi->name;
            delete fi;
            fi = fiHead;
        }
        closedir(d);
        return true;
    }

    bool kLog::initFile() {

        assert(!init);
        assert(isLocked());
        char *file = new char[PATHLEN];
        if (!file) {
            fprintf(stderr, "Error: new file name failed\n");
            return false;
        }
        getFileName(file);
        logFile = fopen(file, "a");
        if (!logFile) {
            fprintf(stderr, "Error: Open file %s failed! (%s)\n",
                    file, strerror(errno));
            return false;
        }
        //fileNames.push(file);
        if (setvbuf(logFile, logBuf, _IOLBF, BUFSIZE) != 0) {
            perror("Error: Set log file line buff failed");
        }

        init = true;

        struct stat st;
        st.st_size = 0;
        fstat(fileno(logFile), &st);
        logSize = st.st_size;
        return true;
    }

    bool kLog::renameFile() {
        char oldFileName[PATHLEN] = {0};
        char newFileName[PATHLEN] = {0};
        char *file;

        // Remove the oldest one as needed

        if (logFileNum >= maxLogFileNum) {
            file = fileNames.front();
            fileNames.pop();
            if (remove(file) < 0) {
                fprintf(stderr, "Remove oldest log file %s error! (%s)\n",
                        file, strerror(errno));
            }
            delete [] file;
        } else {
            logFileNum++;
        }

        getFileName(oldFileName);
        char tmp[15];
        time_t t = time(NULL);

        strftime(tmp, 15, TIME_FMT, localtime(&t));

        sprintf(newFileName, "%s.%s", oldFileName, tmp);
        if (rename(oldFileName, newFileName) < 0) {
            fprintf(stderr, "Rename %s to %s error! (%s)\n",
                    oldFileName, newFileName, strerror(errno));
        }

        file = new char[PATHLEN];
        if (!file) {
            fprintf(stderr, "Error: new file name failed\n");
        }
        strncpy(file, newFileName, PATHLEN);
        fileNames.push(file);
        return true;
    }

    bool kLog::setLogFile(char* path, char* fname) {
        lock();
        closeFile();
        deleteFileNames();

        snprintf(logPath, PATHLEN, "%s", path);
        snprintf(fileName, PATHLEN, "%s", fname);
        fileNameLen = strlen(fileName);

        buildFileNameList();
        initFile();
        unlock();
        return init;
    }

    void kLog::updateFile() {
        assert(isLocked());
        if (!init) {
            initFile();
        }

        // Need to change file
        if (fileSize < logSize) {
            closeFile();
            renameFile();
            initFile();
        }
    }

    void kLog::klog(kLogLevel level, const char* logFormat, va_list args) {
        // Get the time first because the lock() might block us
        time_t t = time(NULL);

        lock();

        if (!logFormat || (logLevel < level)) {
            unlock();
            return;
        }

        updateFile();

        if (!init) {
            unlock();
            return;
        }

        //va_list args;
        //va_start(args, logFormat);

        char prefix[100] = {0};
        char logstr[LOGLINE_LEN] = {0};
        char id[10] = {0};
        char timestr[20] = {0};
        char host[30] = {0};
        if (isAddSeqIdLocked()) {
            sprintf(id, "%5llu:", seqNum);
        }
        seqNum++;

        if (isAddHostnameLocked()) {
            gethostname(host, 30);
        }

        if (isAddTimeLocked()) {
            strftime(timestr, 20, "%Y-%m-%d-%H-%M-%S", localtime(&t));
        }

        sprintf(prefix,
                "%s" // Seq Id
                "%s%s" // Host name
                "%s%s" // Log Time
                "%d: ", // Log Level
                id,
                host, isAddHostnameLocked()?":":"",
                timestr, isAddTimeLocked()?":":"",
                level
               );
        vsnprintf(logstr, LOGLINE_LEN, logFormat, args);
        logSize += fprintf(logFile, "%s%s", prefix, logstr);
        va_end(args);
        unlock();
        return;
    }
}

/**********************************************************************
 * Exported functions                                                 *
 *********************************************************************/

// Global object
kLog log;


bool setLogFile(char* logPath, char* filename)
{
    return log.setLogFile(logPath, filename);
}

void getLogFile(char* logPath, char* filename)
{
    log.getLogFile(logPath, filename);
}

void setLogFileSize(unsigned long long filesize)
{
    log.setLogFileSize(filesize);
}

unsigned long long getLogFileSize()
{
    return log.getLogFileSize();
}

void setMaxLogFileNum(unsigned int logFileNum)
{
    log.setMaxLogFileNum(logFileNum);
}

unsigned int getMaxLogFileNum()
{
    return log.getMaxLogFileNum();
}

void setLogLevel(kLogLevel level)
{
    log.setLogLevel(level);
}

kLogLevel getLogLevel()
{
    return log.getLogLevel();
}

void addTime(bool addTime)
{
    log.addTime(addTime);
}

bool isAddTime()
{
    return log.isAddTime();
}

void addSeqId(bool addId)
{
    log.addSeqId(addId);
}

bool isAddSeqId()
{
    return log.isAddSeqId();
}

void klog(kLogLevel logLevel, const char* logFormat, ...)
{
    va_list args;
    va_start(args, logFormat);
    log.klog(logLevel, logFormat, args);
    va_end(args);
}
